﻿using System;
using System.Diagnostics.Contracts;

namespace Postnuclear
  {
  /// <summary>
  /// Represents a type safe weak reference, which references an object while still allowing
  /// that object to be reclaimed by garbage collection.
  /// </summary>
  /// <typeparam name="TTarget">The type of the target.</typeparam>
  public sealed class WeakReference<TTarget>
    where TTarget : class
    {
    #region Public

    // Constructors

    /// <summary>
    /// Initializes a new instance of the <see cref="WeakReference{TTarget}"/> class.
    /// </summary>
    /// <param name="target">The object to track or null.</param>
    /// <param name="trackResurrection">
    /// Indicates when to stop tracking the object. If <c>true</c>, the object is tracked
    /// after finalization; if <c>false</c>, the object is only tracked until finalization.
    /// </param>
    public WeakReference(TTarget target, bool trackResurrection = false)
      {
      Contract.Ensures(object.Equals(Target, target));

      Reference = new WeakReference(target, trackResurrection);
      }

    // Properties

    /// <summary>
    /// Gets a value indicating whether the object referenced by the current <see cref="T:System.WeakReference"/> object has been garbage collected.
    /// </summary>
    /// <returns><c>true</c> if the object referenced by the current <see cref="T:System.WeakReference"/> object has not been garbage collected
    /// and is still accessible; otherwise, <c>false</c>.</returns>
    public bool IsAlive
      {
      [Pure]
      get
        {
        return Reference.IsAlive;
        }
      }

    /// <summary>
    /// Gets a value indicating whether the object referenced by the current <see cref="T:System.WeakReference"/> object is tracked after it is finalized.
    /// </summary>
    /// <returns><c>true</c> if the object the current <see cref="T:System.WeakReference"/> object refers to is tracked after finalization;
    /// or <c>false</c> if the object is only tracked until finalization.</returns>
    public bool TrackResurrection
      {
      [Pure]
      get
        {
        return Reference.TrackResurrection;
        }
      }

    /// <summary>
    /// Gets or sets the object (the target) referenced by the current <see cref="WeakReference{TTarget}"/> object.
    /// </summary>
    /// <returns>null if the object referenced by the current <see cref="WeakReference{TTarget}"/> object has been garbage collected; otherwise, a reference to the object referenced by the current <see cref="T:System.WeakReference"/> object.</returns>
    /// <exception cref="T:System.InvalidOperationException">The reference to the target object is invalid. This exception can be thrown while setting this property if the value is a null reference or if the object has been finalized during the set operation.</exception>
    public TTarget Target
      {
      get
        {
        return IsAlive ? (TTarget)Reference.Target : null;
        }

      set
        {
        Reference.Target = value;
        }
      }

    // Methods

    /// <summary>
    /// Casts an object of the type <see cref="T:TTarget"/> to a <see cref="WeakReference{TTarget}"/>.
    /// </summary>
    /// <param name="target">The target.</param>
    /// <returns>The result of the conversion.</returns>
    public static implicit operator WeakReference<TTarget>(TTarget target)
      {
      return new WeakReference<TTarget>(target);
      }

    /// <summary>
    /// Casts an object of the type <see cref="WeakReference{TTarget}"/> to a <see cref="T:TTarget"/>.
    /// </summary>
    /// <param name="reference">The reference.</param>
    /// <returns>The result of the conversion.</returns>
    public static implicit operator TTarget(WeakReference<TTarget> reference)
      {
      Contract.Requires<InvalidOperationException>(reference != null);

      return reference.Target;
      }

    /// <summary>
    /// Implements the operator ==.
    /// </summary>
    /// <param name="lhs">The left-hand side.</param>
    /// <param name="rhs">The right-hand side.</param>
    /// <returns>
    /// <c>true</c> if <paramref name="lhs"/> equals <paramref name="rhs"/>; otherwise, <c>false</c>.
    /// </returns>
    public static bool operator ==(WeakReference<TTarget> lhs, WeakReference<TTarget> rhs)
      {
      return Equals(lhs, rhs);
      }

    /// <summary>
    /// Implements the operator !=.
    /// </summary>
    /// <param name="lhs">The left-hand side.</param>
    /// <param name="rhs">The right-hand side.</param>
    /// <returns>
    /// <c>true</c> if <paramref name="lhs"/> does not equal <paramref name="rhs"/>; otherwise, <c>false</c>.
    /// </returns>
    public static bool operator !=(WeakReference<TTarget> lhs, WeakReference<TTarget> rhs)
      {
      return !(lhs == rhs);
      }

    /// <summary>
    /// Check equality of the specified slices.
    /// </summary>
    /// <param name="lhs">The left-hand side.</param>
    /// <param name="rhs">The right-hand side.</param>
    /// <returns>
    /// <c>true</c> if the specified instances of <see cref="WeakReference{TTarget}"/> are equal; otherwise, <c>false</c>.
    /// </returns>
    public static bool Equals(WeakReference<TTarget> lhs, WeakReference<TTarget> rhs)
      {
      // A common error in overloads of operator == is to use (a == b), (a == null), or (b == null) to check for reference equality.
      // This instead results in a call to the overloaded operator ==, causing an infinite loop.

      // Use ReferenceEquals
      if (object.ReferenceEquals(lhs, rhs))
        {
        return true;
        }

      // And cast the type to Object, to avoid the loop.
      if (((object)lhs == null) || ((object)rhs == null))
        {
        return false;
        }

      TTarget lhsTarget = lhs.Target; // preserve from being garbage collected
      TTarget rhsTarget = rhs.Target; // preserve from being garbage collected

      if (!lhs.IsAlive && !rhs.IsAlive)
        {
        return true;
        }

      if (!lhs.IsAlive || !rhs.IsAlive)
        {
        return false;
        }

      return object.Equals(lhsTarget, rhsTarget);
      }

    /// <summary>
    /// Determines whether the specified <see cref="System.Object"/> is equal to this instance.
    /// </summary>
    /// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
    /// <returns>
    /// <c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.
    /// </returns>
    public override bool Equals(object obj)
      {
      if (obj == null || obj.GetType() != GetType())
        {
        return false;
        }

      return Equals(this, (WeakReference<TTarget>)obj);
      }

    /// <summary>
    /// Determines whether the specified <see cref="WeakReference{TTarget}"/> is equal to this instance.
    /// </summary>
    /// <param name="other">The <see cref="WeakReference{TTarget}"/> to compare with this instance.</param>
    /// <returns>
    /// <c>true</c> if the specified <see cref="WeakReference{TTarget}"/> is equal to this instance; otherwise, <c>false</c>.
    /// </returns>
    public bool Equals(WeakReference<TTarget> other)
      {
      return Equals(this, other);
      }

    /// <summary>
    /// Returns a hash code for this instance.
    /// </summary>
    /// <returns>
    /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
    /// </returns>
    public override int GetHashCode()
      {
      TTarget target = Target; // preserve target from being garbage collected during we are working with it.

      return target == null ? 0 : target.GetHashCode();
      }

    #endregion

    #region Private

    // Fields

    private readonly WeakReference Reference;

    #endregion
    }
  }